Civic Census is an official program undertaken by Municipal Governments annually and Federal Governments once in five years. Information acquired through this online and door to door program includes, dwelling size, count of residents living in each community or geographic unit. Supplementary information including age, gender and education are also collected on a schedule.
Municipalities find the census information beneficial for planning community services like planning for infrastructure like schools, recreation facilities, senior living arrangements etc. to support the growing population and weigh the effectiveness of the services currently provided.
Our purpose is to analyze the age distribution of City of Calgary. Through visualization we want to share the story of the demographic profile of the city as it comes to the three groups of population namely children (youth) ages 0-14, working population between ages 15-64, and senior population ages 65+.
The datasets that were used in our study are available on the Open Calgary Data Portal shared by the City of Calgary. The data is open to use under the creative commons license https://data.calgary.ca/d/Open-Data-Terms/u45n-7awa. The Civic_Census_by_Community_Age_and_Gender contains age and gender information collected from 1996 to 2019. Additionally the Calgary Community Boundaries shapefile and geojson datasets to map the data spatially.
Our scope of work is to analyze the data for the last five years when age information was collected in the census, i.e 2009,2011,2014,2016,2019.The tabular dataset provides age information in 5 year intervals enumerated at the community level. It also includes the gender information, which has been omitted in the scope of our work. The columns that are of our key interest are the resident count, age ranges, community names and the year the information was collected.
Demographic structure provides an insight of a city’s development. It reveals the trend in which a city is growing towards and provides a guidance to almost every aspect of municipal decision, including health care, education, community development and even immigration policy. This study is to conduct an exploratory analysis on the City of Calgary to explore how the demographic profile variable of age varies.
Is Calgary population growing or declining?
Age distribution of Calgary over the last 5 years?
Is Calgary’s population young or aging?
Which community is aging fastest?
What is the dependency ratio of the child and senior population to the working age population?
To answer our guiding questions, we will use Python within the Jupyter notebook environment to create visualizations of the dataset. The main software libraries we’ll be importing are Pandas, Matplotlib, Geopandas, Pyplot etc.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from plotly.subplots import make_subplots
import plotly as plotly
import plotly.graph_objs as go
import plotly.express as px
import geopandas as gpd
from IPython.display import Image
import json
import imageio
from pathlib import Path
Our cleaning and wrangling activities included:
Filter data: Dataset includes census from year 1996 to 2019. Whereas only 2009 to 2019 are of interest of this project.
Data type conversion: Convert values from string to integer to calculate resident count.
Pivot Age Data: The Age Range information is listed as rows and we need it to be as columns to calculate total of each population age group.
Calculate additional columns: New columns will be added for population totals by age group children, senior and working Aged-child ratio and age dependency ratio are also calculated.
Manipulating data frame: Organize the data frames grouped by Year, Community Name, and Sum of population groups
For the year 2019, an additional column 'OTHER' for LGBTQ count was collected. Therefore, 2019 dataset is extracted separately for further wrangling.
rawDF=pd.read_csv(r'Civic_Census_by_Community__Age_and_Gender.csv')
display(rawDF)
df0916=rawDF[(rawDF.YEAR==2009)|(rawDF.YEAR==2011)|(rawDF.YEAR==2014)|(rawDF.YEAR==2016)].copy()
df2019=rawDF[rawDF.YEAR==2019].copy()
# Delete the Raw Data datafram
del rawDF
# Convert columns 'MALES' and 'FEMALES' datatype to int by handling the thousands values in #,### format.
df0916[['MALES','FEMALES']]=df0916[['MALES','FEMALES']].applymap(lambda x:''.join(x.split(',')))
df0916[['MALES','FEMALES']]=df0916[['MALES','FEMALES']].astype('int')
df2019[['MALES','FEMALES']]=df2019[['MALES','FEMALES']].applymap(lambda x:''.join(x.split(',')))
df2019[['MALES','FEMALES']]=df2019[['MALES','FEMALES']].astype('int')
# Calculate resident_count for each age range by summing up
# - 'MALES' and 'FEMALES' for year 2009-2016.
# - 'MALES' 'FEMALES' and 'OHTER' (LGBTQ counts) for year 2019
df0916['RESIDENT_COUNT']=df0916['MALES']+df0916['FEMALES']
df0916.drop(['MALES','FEMALES','OTHER'],axis=1,inplace=True)
df2019['RESIDENT_COUNT']=df2019['MALES']+df2019['FEMALES']+df2019['OTHER']
df2019.drop(['MALES','FEMALES','OTHER'],axis=1,inplace=True)
# Merge both data sets into one Dataframe
df = pd.concat([df0916,df2019])
# Delete the data frames to free some memory
del df0916
del df2019
# Pivot column 'AGE_RANGE' from rows to columns.
df = df.groupby(['YEAR','COMM_CODE','AGE_RANGE']).RESIDENT_COUNT.sum().unstack().reset_index()
display(df.head())
# Noticed age range 5-14 column is not in the right order, adjust position.
range514=df['5-14']
df.drop('5-14',axis=1,inplace=True)
df.insert(3,r'5-14',range514)
df.columns.name=''
# Delete variable for memory clean up
del range514
# display(df)
# Add an additional column of 'RESIDENT_COUNT' for each community by summing up all age ranges counts.
rescnt=df.set_index(['YEAR','COMM_CODE']).apply(lambda x: np.sum(x),axis=1)
rescnt.index=range(len(df))
df.insert(2,'RESIDENT_COUNT',rescnt)
# deleting the variable to save memory
del rescnt
# Communities with zero residents needs to be filtered out, such as industrial zones.
df.dropna(inplace=True)
df.drop(df[df['RESIDENT_COUNT']==0].index,axis=0,inplace=True)
df[['RESIDENT_COUNT','0-4','5-14','15-19','20-24','25-34','35-44','45-54','55-64','65-74','75+']]=df[['RESIDENT_COUNT','0-4','5-14','15-19','20-24','25-34','35-44','45-54','55-64','65-74','75+']].astype('int')
df.COMM_CODE=df.COMM_CODE.astype('str')
df.index=range(1,len(df)+1)
df
df['CHILD_COUNT'] = df['0-4'] + df['5-14']
df['MIDDLE_COUNT'] = df['15-19'] + df['20-24'] + df['25-34'] + df['35-44'] + df['45-54'] + df['55-64']
df['SR_COUNT'] = df['65-74'] + df['75+']
# Capture the data columns required for further analysis into a seperate dataframe
finalDF = pd.DataFrame({'YEAR' : df['YEAR'], 'COMM_CODE': df['COMM_CODE'],'YOUNG_AGE_COUNT' : df['CHILD_COUNT'], 'WORKING_AGE_COUNT' : df['MIDDLE_COUNT'], 'SR_AGE_COUNT' : df['SR_COUNT'], 'TOTAL_RESIDENT_COUNT' : df['RESIDENT_COUNT']})
# Delete the previos dataframe to release some memory allocation
del df
display(finalDF)
# Calculate the total resident count for each year
yearlyCountsDF = finalDF.groupby('YEAR', as_index=False).sum()
yearlyCountsDF['YEAR_SHIFT'] = yearlyCountsDF['YEAR']-yearlyCountsDF['YEAR'].shift(1)
# Population growth rate
yearlyCountsDF['GROWTH_RATE'] = (yearlyCountsDF.pct_change()['TOTAL_RESIDENT_COUNT']/yearlyCountsDF['YEAR_SHIFT'])*100
display(yearlyCountsDF)
fig1=make_subplots(specs=[[{'secondary_y':True}]])
trace1=go.Bar(x=yearlyCountsDF['YEAR'],y=yearlyCountsDF['TOTAL_RESIDENT_COUNT'],name='Population',marker_opacity=0.7, width=0.3)
trace2=go.Scatter(x=yearlyCountsDF['YEAR'],y=yearlyCountsDF['GROWTH_RATE'],name='Population Growth Rate',marker_color='blue')
fig1.add_trace(trace1,secondary_y=False)
fig1.add_trace(trace2,secondary_y=True)
fig1.update_xaxes(title_text='Census Year',type='category')
fig1.update_yaxes(title_text='Population',secondary_y=False)
fig1.update_yaxes(title_text='Population Growth Rate in percent',secondary_y=True)
fig1.update_layout(title_text='Calgary Population and Growth rate',title_font_size=25, title_x=0.45,title_y=0.95,height=650)
fig1.show()
Calgary population has grown from 1.06 million in 2009 to 1.28 million in 2019. The every census year.
Population grew fastest from 2011 to 2014 (rate of 3.18 percent), which might relate to oil and gas industry prosperity. Growing pace has slowed down from 2014 to 2016, similarly, might relate to the economic downturn of oil and gas industry.
fig = go.Figure(data=[
go.Bar(name='Young - [0-14]', x=yearlyCountsDF['YEAR'], y=yearlyCountsDF['YOUNG_AGE_COUNT'], marker_color='steelblue'),
go.Bar(name='Working - [15-14]', x=yearlyCountsDF['YEAR'], y=yearlyCountsDF['WORKING_AGE_COUNT'],marker_color='firebrick'),
go.Bar(name='Senior - [65+]', x=yearlyCountsDF['YEAR'], y=yearlyCountsDF['SR_AGE_COUNT'])
])
fig.update_xaxes(title_text='Census Year', type='category')
fig.update_yaxes(title_text='Population')
# Change the bar mode
fig.update_layout(barmode='group', title_text='Age Distribution of Calgary', title_font_size=25, title_x=0.475,title_y=0.95, height=650)
fig.show()
# Calculate the increments over the period
increment=yearlyCountsDF-yearlyCountsDF.shift(axis=0)
increment['YEAR']= yearlyCountsDF['YEAR']
increment['YOUNG_INC_PRCNT']=increment['YOUNG_AGE_COUNT']*100/increment['TOTAL_RESIDENT_COUNT']
increment['WORKING_INC_PRCNT']=increment['WORKING_AGE_COUNT']*100/increment['TOTAL_RESIDENT_COUNT']
increment['SR_INC_PRCNT']=increment['SR_AGE_COUNT']*100/increment['TOTAL_RESIDENT_COUNT']
#Cleanup Dataframe
increment.drop(0, inplace=True)
increment.drop(['YOUNG_AGE_COUNT','WORKING_AGE_COUNT','SR_AGE_COUNT','TOTAL_RESIDENT_COUNT','YEAR_SHIFT','GROWTH_RATE'], axis=1, inplace=True)
display(increment)
# Plot the increments
fig = go.Figure(data=[
go.Bar(x=increment['YEAR'],y=increment['YOUNG_INC_PRCNT'],name='Young - [0-14]',width=0.2),
go.Bar(x=increment['YEAR'],y=increment['WORKING_INC_PRCNT'],name='Working - [15-14]',width=0.2),
go.Bar(x=increment['YEAR'],y=increment['SR_INC_PRCNT'],name='Senior - [65+]',width=0.2)
])
fig.update_xaxes(type='category')
# Change the bar mode
fig.update_layout(barmode='stack',title_text='Age Distribution of Incremental Population',
title_font_size=25, title_x=0.475,title_y=0.95,height=650,
xaxis_title_text='Census Year',
yaxis_title_text='Percentage of Increment'
)
fig.show()
Provides the insight of whether a city is young or aging. An animated bar charts will be utilized to check the detailed age structure change over years. The Aged-Child ratio is calculated using the following equation (Lacey & Speizer, 2015) :
yearlyCountsDF['AGED_CHILD_RATIO'] = yearlyCountsDF['SR_AGE_COUNT']/yearlyCountsDF['YOUNG_AGE_COUNT']
display(yearlyCountsDF)
fig = go.Figure(data=[
go.Scatter(x=yearlyCountsDF['YEAR'], y=yearlyCountsDF['AGED_CHILD_RATIO'])
])
fig.update_xaxes(title_text='Census Year', type='category')
fig.update_yaxes(title_text='Aged vs Young ratio')
fig.update_layout(title_text='Aged-Child Ratio of Calgary', title_x=0.5,title_y=0.95, title_font_size=25, height=650)
fig.show()
The above plot represents the Aged-Child ratio is increasing over the years i.e the growth in Senior population is more than Younger population.
The ratio is expressed as percentage per 100 persons. This statistic describes how many elderly exist per 100 children in a community. The higher the ratio the more elderly in a community, and lower the ratio the younger the community is.
The Community Census 2019 geojson file is read into geopandas dataframe to add the Community name as id column to the existing tabular pandas dataframe. Secondary community codes that were not in the geojson file are removed from the dataframe. Further to avoid to dive by zero (infinity) values all rows that have zero as total in Senior, Working, Young Age count columns are dropped from the dataframe.
#communities that do not exist in geojson boundary data
comm_noBound=['CFC','CFL','CON','DOU','SKY','SYV,','VIC','X01','X02','X03','X04','X05','X06','X07','X08','X09','X10','X11','X12','X13','X14']
finalDF[finalDF.COMM_CODE.isin(comm_noBound)]
finalDF = finalDF[~finalDF.COMM_CODE.isin(comm_noBound)]
finalDF
#Replace zero to NAN and drop them as they cause inf values
finalDF.replace(0, np.nan, inplace=True)
finalDF.dropna(inplace=True)
finalDF
#Calculate Aged-Child Ratio for every community
finalDF.loc[:,'AGEDCHILDRATIO'] = (finalDF['SR_AGE_COUNT'] / finalDF['YOUNG_AGE_COUNT'])*100
#Calculate Child Dependency Ratio per community
#child dependency ratio is caluated as child population /working population *100
finalDF.loc[:,'AGE_DEPENDENCY_RATIO'] = (finalDF['YOUNG_AGE_COUNT']+finalDF['SR_AGE_COUNT'])/ finalDF['WORKING_AGE_COUNT'] *100
finalDF
#https://plotly.com/python/choropleth-maps/
#Prepare geojson data to join and plot a choropleth map
calgary_communities = ("Census2019.geojson")
comm_df = gpd.read_file(calgary_communities)
comm_df.columns
comm_df= comm_df[['comm_code','name','geometry']]
#comm_df.dtypes
#Projection Information
comm_df.crs
#Reproject to NAD83 #TM
comm_df.to_crs("EPSG:3776")
#comm_df.plot()
calgary_communities=json.load(open("Census2019.geojson",'r'))
calgary_communities['features'][0].keys()
community_name={}
for feature in calgary_communities['features']:
feature['id']=feature['properties']['name']
community_name[feature['properties']['comm_code']]=feature['id']
community_name
finalDF['NAME']=finalDF['COMM_CODE'].apply(lambda x: community_name[x])
finalDF
#https://cmdlinetips.com/2019/03/how-to-select-top-n-rows-with-the-largest-values-in-a-columns-in-pandas/
# Top Three Aging Communities in Calgary in the last five years
seriesAging=finalDF.set_index('NAME').groupby('YEAR')['AGEDCHILDRATIO'].nlargest(3)
dfAging=seriesAging.to_frame().reset_index()
dfAging
seriesYoung=finalDF.set_index('NAME').groupby('YEAR')['AGEDCHILDRATIO'].nsmallest(3)
dfYoung=seriesYoung.to_frame().reset_index()
dfYoung
CommunityBoundaries = ("CommunityBoundaries.shp")
commShp_df = gpd.read_file(CommunityBoundaries)
commShp_df.columns
commShp_df= commShp_df[commShp_df['class'] == 'Residential']
commShp_df.drop(columns=['class_code','comm_struc','sector','srg'],axis=1,inplace=True)
commShp_df
An outlier in the data for the year 2009, Downtown East Village has a significantly higher ratio of 9883.00 and has changed over the decade and in the year 2019 has ratio of 544. This could be the result of new immigration migration that welcomed younger populations in that community.
The communities of Downtown East Village, Chinatown and Eagle Ridge consistently had high ratios, to conclude that City central district has more aging population. Communities of Auburn Bay, Mahogany, Nolan Hill, Evanston, Redstone have very low Aged- Child ratios concluding that they have more children vs the elderly.
A choropleth map is a thematic map that describes the geographic distribution of a statistic variable. The values are represented in a shade or intensity of a hue to show a pattern (Wikipedia, Choropleth map 2020). Often the measure is a statistical variable that is normalized. The Age dependency ratio and Aged Child ratio can be portrayed as a choropleth map to visualize how the measurement varies across Calgary communities.
To visualize the pandas dataframe data spatially, we read the shapefile of Calgary community boundaries into a new geopandas dataframe and join the pandas dataframe. The join is based on the community name which we attributed to the data in the previous step using the geojson file. The projection of the shapefile was changed from EPSG 4326 (geographic coordinate system) to EPSG 3776 (projected coordinate system) a mercator projection that preserves the shape at a local scale.
Each year ratios for the variables (Aged Child Ratio, Age Dependency Ratio) are added as individual columns to the shapefile data. The plots are made using the Geopandas.Dataframe.plot method.
The map classification scheme used is fisher-jenks natural breaks, this classification uses the Jenks Natural breaks optimization method, where data is divided into classes at natural breaks occurring in the data.
#Ref (Kwong et al., 2019)
ChoroplethDF = pd.DataFrame(finalDF.groupby(['NAME','YEAR'])[['AGEDCHILDRATIO','AGE_DEPENDENCY_RATIO']].sum()).reset_index()
# filter rows based on year and add as columns to join
year_09 = ChoroplethDF[ChoroplethDF['YEAR'] == 2009].drop(columns=['YEAR']).rename(index=str, columns={'AGEDCHILDRATIO': '2009_ACRATIO','AGE_DEPENDENCY_RATIO': '2009_ADRATIO'}).set_index('NAME')
year_11 = ChoroplethDF[ChoroplethDF['YEAR'] == 2011].drop(columns=['YEAR']).rename(index=str, columns={'AGEDCHILDRATIO': '2011_ACRATIO','AGE_DEPENDENCY_RATIO': '2011_ADRATIO'}).set_index('NAME')
year_14 = ChoroplethDF[ChoroplethDF['YEAR'] == 2014].drop(columns=['YEAR']).rename(index=str, columns={'AGEDCHILDRATIO': '2014_ACRATIO','AGE_DEPENDENCY_RATIO': '2014_ADRATIO'}).set_index('NAME')
year_16 = ChoroplethDF[ChoroplethDF['YEAR'] == 2016].drop(columns=['YEAR']).rename(index=str, columns={'AGEDCHILDRATIO': '2016_ACRATIO','AGE_DEPENDENCY_RATIO': '2016_ADRATIO'}).set_index('NAME')
year_19 = ChoroplethDF[ChoroplethDF['YEAR'] == 2019].drop(columns=['YEAR']).rename(index=str, columns={'AGEDCHILDRATIO': '2019_ACRATIO','AGE_DEPENDENCY_RATIO': '2019_ADRATIO'}).set_index('NAME')
# Join all years AGEDCHILDRATIO Data to Community Boundaries Shapefile Data
MapDF = commShp_df.set_index('name').join([year_09,year_11,year_14,year_16,year_19])
MapDF['NAME'] = MapDF.index
MapDF
#https://pbpython.com/natural-breaks.html
year =['2009','2011','2014','2016','2019']
# list of years to iterate
# loop year to create map for each year
for y in year:
col= y+"_ACRATIO"
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
Map = MapDF.plot(column=col, scheme='fisher_jenks', k=8, cmap="OrRd", linewidth=0.3, ax=ax, edgecolor="grey", legend=True, legend_kwds={'loc': 'best', 'bbox_to_anchor':(0.6, 0.5, 0.5, 0.5)}, missing_kwds={"color": "lightgrey","edgecolor": "grey","hatch": "///", "label": "No Data"})
Map.set_title("Aged-Child Ratio of Calgary in "+str(y), fontdict={"fontsize": "20", "fontweight" : "4", "fontstyle" :"italic"})
Map.annotate("Ratio expressed per 100 persons"+'\n\n'+"Source: City of Calgary Open Data Hub, 2020",xy=(0.1, 0.08), xycoords="figure fraction", horizontalalignment="left", verticalalignment="bottom", fontsize=10, color="#555555")
legend = ax.get_legend()
ax.set_axis_off()
fig.set_size_inches(10,16)
fig.savefig(str(y)+"AgedChildRatio.png", dpi=300)
#https://medium.com/swlh/python-animated-images-6a85b9b68f86
#create animated gif from images
images_path=Path('')
images= list(images_path.glob('*AgedChildRatio.png'))
image_list=[]
for file_name in sorted(images):
image_list.append(imageio.imread(file_name))
imageio.mimwrite('AgedChildRatioMapAnimation.gif',image_list,fps=100, duration=0.5)
with open('AgedChildRatioMapAnimation.gif','rb') as f:
display(Image(data=f.read(), format='png'))
The dependent population includes children and the elderly that are not in the workforce. This population includes children and the elderly who typically rely on others for support. The remainder of the population makes up the working-age population.
Age Dependency Ratio is a relative measure of the working-age population supporting the non-working age population. This measure is used to express the relationship between three age groups within a population:
The higher the ratio, the greater the burden. Lower ratios indicate more people are workforce that can support the dependent population. However, the limitation of using the Age Dependency ratio is, it does not include the population aged 65 and over who are still in the workforce and also population aged 15 to 64 that are not currently in the workforce.
year =['2009','2011','2014','2016','2019']
# list of years to iterate
# loop year to create map for each year
for y in year:
col= y+"_ADRATIO"
fig, ax = plt.subplots(1, 1)
Map = MapDF.plot(column=col, scheme='fisher_jenks', k=8, cmap="YlGnBu", linewidth=0.3, ax=ax, edgecolor="grey", legend=True, legend_kwds={'loc': 'best', 'bbox_to_anchor':(0.6, 0.5, 0.5, 0.5)}, missing_kwds={"color": "lightgrey","edgecolor": "grey","hatch": "///", "label": "No Data"})
Map.set_title("Age Dependency Ratio of Calgary in "+str(y), fontdict={"fontsize": "20", "fontweight" : "4", "fontstyle" :"italic"})
# position the annotation to the bottom left
Map.annotate("Dependecy ratio per 100 working persons"+'\n\n'+"Source: City of Calgary Open Data Hub, 2020",xy=(0.1, 0.08), xycoords="figure fraction", horizontalalignment="left", verticalalignment="bottom", fontsize=10, color="#555555")
leg = ax.get_legend()
ax.set_axis_off()
fig.set_size_inches(10,16)
fig.savefig(str(y)+"AgeDependencyRatio.png", dpi=300)
images_path=Path('')
images= list(images_path.glob('*AgeDependencyRatio.png'))
image_list=[]
for file_name in sorted(images):
image_list.append(imageio.imread(file_name))
imageio.mimwrite('AgeDependencyRatioMapAnimation.gif',image_list,fps=100, duration=0.5)
with open('AgeDependencyRatioMapAnimation.gif','rb') as f:
display(Image(data=f.read(), format='png'))
Throughout our analysis, we observed that total population of the city steadily increased from 2009 to 2019, with a spike between 2011 and 2014 which can be attributed to the good economic conditions around the oil industry just before the economic downturn near the end of 2014.
The 2019 composition of the population shows that the City is aging with more dependent senior population clustered around the City's Downtown Core and mature neighborhoods built around 70's. A high Age Dependency Ratio is observed in the communities of Pumphill, Mission, Chinatown, Varsity, Douglas Dale/Glen, Eagle Ridge, Country Hills Village.
Overall, concentration of the populations reveal a pattern where younger age groups are located around the University District and away from the City's downtown core, extending to the developing suburban areas in the City's vicinity. The City younger populations are in the communities of Evanston, New Brighton, Copperfield, Nolan Hill.
These ratio would help municipal planning authorities to determine the type of services a community needs varying from new schools, transportation services, retirement homes, recreation and health care facilities etc.
Further study of the age data combined with the gender information and population pyramids would reveal patterns in the data for gender distribution around the city. Evaluating additional socio-economic variables like median income and level of education will share a greater insight surrounding the working age population's burden supporting economically dependent populations.
Further study of the age data combined with the gender information and population pyramids would reveal patterns in the data for gender distribution around the city. Evaluating additional socio-economic variables like median income and level of education will share a greater insight surrounding the working age population's burden supporting economically dependent populations.
Cooley, B. (2018) Let’s make a map! Using Geopandas, Pandas and Matplotlib to make a Choropleth map [Online]. Available at: https://towardsdatascience.com/lets-make-a-map-using-geopandas-pandas-and-matplotlib-to-make-a-chloropleth-map-dddc31c1983d [Accessed October 16 2020]
Yong Cui, P. D. (2020, January 23). Create Animated Images Using Python. Available at https://medium.com/swlh/python-animated-images-6a85b9b68f86. [Accessed October 16 2020]
Anon, 2016. Seniors and Aging Population. The City of Calgary - Home Page. Available at: https://www.calgary.ca/csps/cns/research-and-strategy/seniors-and-aging-population.html [Accessed September 26, 2020].
Chappelow, J.I., 2020. Demographics Definition. Investopedia. Available at: https://www.investopedia.com/terms/d/demographics.asp [Accessed September 27, 2020)].
Findley, S.E., Measure of the Total Population Structure and Size. Demography. Available at: http://www.columbia.edu/itc/hs/pubhealth/modules/demography/population.html [Accessed September 26, 2020].
Lacey, L., 2015. Lesson 3: Creating a Demographic Profile I. Speizer, ed. Lesson 3: Creating a Demographic Profile - MEASURE Evaluation. Available at: https://www.measureevaluation.org/resources/training/online-courses-and-resources/non-certificate-courses-and-mini-tutorials/population-analysis-for-planners/lesson-3 [Accessed September 27, 2020].
Kwong, Cheryl, Drew Anweiler, and Mary Sarafraz. “CRIME STATISTICS DATA ANALYTICS.” Scholars Portal Dataverse. Scholars Portal Dataverse, (January 17, 2019). https://dataverse.scholarsportal.info/dataset.xhtml?persistentId=doi%3A10.5683%2FSP2%2FIE6NRY. [Accessed October 21 2020].
esri data development, 2020. How to Use and Interpret Esri's US Age Dependency Ratio Data. ArcGIS StoryMaps. Available at: https://storymaps.arcgis.com/stories/cbc77e4c2b304e87b38111d872cb11f5 [Accessed October 21, 2020].
Moffitt, C., 2019. Finding Natural Breaks in Data with the Fisher-Jenks Algorithm. Practical Business Python Atom. Available at: https://pbpython.com/natural-breaks.html [Accessed October 21, 2020].